#include "umlrelation.h"
#include "uml/umlrelationalitem.h"
#include "uml/relations/ports/relationport.h"


UmlRelation::UmlRelation(RelationTypes relationType, QGraphicsItem *parent)
{
    Q_UNUSED(parent);

    this->_port1 = 0;
    this->_port2 = 0;

    this->_itemTypeFlags |= UmlItem::UmlRelationType;

    this->_relationType = 0;
    if (relationType == UmlAssociationRelationType ||
        relationType == UmlAggregationRelationType ||
        relationType == UmlCompositionRelationType) {
        this->_relationType = UmlUndirectedRelationType;
    } else {
        this->_relationType = UmlDirectedRelationType;
    }

    this->_relationType |= relationType;

    this->setGraphicsEffect(0);
    this->setZValue(-1);
    this->setFlags(QGraphicsItem::ItemIsMovable & ~QGraphicsItem::ItemIsMovable);
    this->setHandlesChildEvents(false);

    this->_pen.setWidth(1);
    this->_pen.setColor(Qt::black);
    this->_pen = this->linePenForRelationType(relationType);

    this->_destinationItem = 0;

    this->_crossLineGraphicsItem.setPen(this->_pen);

    this->_crossLineGraphicsItem.setZValue(std::numeric_limits<int>::min() + 1);
    this->addToGroup(&this->_crossLineGraphicsItem);
    QObject::connect(&this->_crossLineGraphicsItem, SIGNAL(relationHoverEnter(QGraphicsSceneHoverEvent*)), this, SLOT(relationHoverEnter(QGraphicsSceneHoverEvent*)));
    QObject::connect(&this->_crossLineGraphicsItem, SIGNAL(relationHoverLeave(QGraphicsSceneHoverEvent*)), this, SLOT(relationHoverLeave(QGraphicsSceneHoverEvent*)));
    QObject::connect(&this->_crossLineGraphicsItem, SIGNAL(relationHoverMove(QGraphicsSceneHoverEvent*)), this, SLOT(relationHoverMove(QGraphicsSceneHoverEvent*)));
    QObject::connect(&this->_crossLineGraphicsItem, SIGNAL(relationMouseDoubleClick(QGraphicsSceneMouseEvent*)), this, SLOT(relationMouseDoubleClick(QGraphicsSceneMouseEvent*)));

    this->_extensionLine1GraphicsItem.setPen(this->_pen);
    this->_extensionLine1GraphicsItem.setZValue(std::numeric_limits<int>::min() + 1);
    this->addToGroup(&this->_extensionLine1GraphicsItem);
    QObject::connect(&this->_extensionLine1GraphicsItem, SIGNAL(relationHoverEnter(QGraphicsSceneHoverEvent*)), this, SLOT(relationHoverEnter(QGraphicsSceneHoverEvent*)));
    QObject::connect(&this->_extensionLine1GraphicsItem, SIGNAL(relationHoverLeave(QGraphicsSceneHoverEvent*)), this, SLOT(relationHoverLeave(QGraphicsSceneHoverEvent*)));
    QObject::connect(&this->_extensionLine1GraphicsItem, SIGNAL(relationHoverMove(QGraphicsSceneHoverEvent*)), this, SLOT(relationHoverMove(QGraphicsSceneHoverEvent*)));
    QObject::connect(&this->_extensionLine1GraphicsItem, SIGNAL(relationMouseDoubleClick(QGraphicsSceneMouseEvent*)), this, SLOT(relationMouseDoubleClick(QGraphicsSceneMouseEvent*)));

    this->_extensionLine2GraphicsItem.setPen(this->_pen);
    this->_extensionLine2GraphicsItem.setZValue(std::numeric_limits<int>::min() + 1);
    this->addToGroup(&this->_extensionLine2GraphicsItem);
    QObject::connect(&this->_extensionLine2GraphicsItem, SIGNAL(relationHoverEnter(QGraphicsSceneHoverEvent*)), this, SLOT(relationHoverEnter(QGraphicsSceneHoverEvent*)));
    QObject::connect(&this->_extensionLine2GraphicsItem, SIGNAL(relationHoverLeave(QGraphicsSceneHoverEvent*)), this, SLOT(relationHoverLeave(QGraphicsSceneHoverEvent*)));
    QObject::connect(&this->_extensionLine2GraphicsItem, SIGNAL(relationHoverMove(QGraphicsSceneHoverEvent*)), this, SLOT(relationHoverMove(QGraphicsSceneHoverEvent*)));
    QObject::connect(&this->_extensionLine2GraphicsItem, SIGNAL(relationMouseDoubleClick(QGraphicsSceneMouseEvent*)), this, SLOT(relationMouseDoubleClick(QGraphicsSceneMouseEvent*)));

    this->_nameLabel.setZValue(std::numeric_limits<int>::max());
    this->_nameLabel.setVisible(false);
    this->_nameLabel.setBrush(this->_palette.relationNameBackground());
    this->addToGroup(&this->_nameLabel);
    QObject::connect(&this->_nameLabel, SIGNAL(labelDoubleClick(QGraphicsSceneMouseEvent*)), this, SLOT(relationMouseDoubleClick(QGraphicsSceneMouseEvent*)));
}

UmlRelation::~UmlRelation()
{
}

bool UmlRelation::relationIsOfType(RelationTypes relationType)
{
    return (this->_relationType & relationType) > 0;
}

UmlRelation::RelationTypes UmlRelation::relationType()
{
    return this->_relationType;
}

void UmlRelation::setRelationType(UmlRelation::RelationTypes relationType)
{
    this->_relationType = 0;
    this->_relationType = UmlUndirectedRelationType;
    this->_relationType |= relationType;

    if (this->_port1) {
        this->_port1->setRelation(this);
    }

    if (this->_port2) {
        this->_port2->setRelation(this);
    }
}

void UmlRelation::setText(QString name)
{
    this->_name = name;
    this->_umlText = name;


    this->_nameLabel.setText(name);
    this->_nameLabel.setVisible(true);

    this->drawRelationLine();
}

UmlRelationalItem* UmlRelation::item1()
{
    return this->_item1;
}

UmlCardinality UmlRelation::cardinalityItem1() const
{
    return this->_cardinalityItem1;
}

void UmlRelation::setCardinalityItem1(const UmlCardinality cardinality)
{
    this->_cardinalityItem1 = cardinality;

    RelationPort *port = this->_item1->portForRelation(this, this->_item1->storedRelationDirection(this));
    port->setCardinality(cardinality);
}

QString UmlRelation::roleItem1()
{
    return this->_roleItem1;
}

void UmlRelation::setRoleItem1(QString role)
{
    this->_roleItem1 = role;

    RelationPort *port = this->_item1->portForRelation(this, this->_item1->storedRelationDirection(this));
    port->setRole(role);
}

RelationPort* UmlRelation::port1()
{
    return this->_port1;
}

UmlRelation::RelativeItemPosition UmlRelation::item1Direction()
{
    return this->_item1Direction;
}

UmlRelationalItem* UmlRelation::item2()
{
    return this->_item2;
}

UmlCardinality UmlRelation::cardinalityItem2() const
{
    return this->_cardinalityItem2;
}

void UmlRelation::setCardinalityItem2(const UmlCardinality cardinality)
{
    this->_cardinalityItem2 = cardinality;

    RelationPort *port = this->_item2->portForRelation(this, this->_item2->storedRelationDirection(this));
    port->setCardinality(cardinality);
}

QString UmlRelation::roleItem2()
{
    return this->_roleItem2;
}

void UmlRelation::setRoleItem2(QString role)
{
    this->_roleItem2 = role;

    RelationPort *port = this->_item2->portForRelation(this, this->_item2->storedRelationDirection(this));
    port->setRole(role);
}

RelationPort* UmlRelation::port2()
{
    return this->_port2;
}

UmlRelation::RelativeItemPosition UmlRelation::item2Direction()
{
    return this->_item2Direction;
}

UmlRelationalItem* UmlRelation::destinationItem()
{
    return this->_destinationItem;
}

bool UmlRelation::isDirected()
{
    return this->relationIsOfType(UmlRelation::UmlGeneralizationRelationType) || this->relationIsOfType(UmlRelation::UmlRealizationRelationType);
}

void UmlRelation::setDirectionTowards(UmlRelationalItem *item)
{
    if (item != this->_item1 && item != this->_item2) {
        return;
    }

    this->_destinationItem = item;

    this->_extensionLine2GraphicsItem.setVisible(false);

    emit this->directionHasChanged();
}

void UmlRelation::removeDirection()
{
    if (this->_destinationItem) {
        this->_destinationItem = 0;
    }

    this->_extensionLine2GraphicsItem.setVisible(true);

    emit this->directionHasChanged();
}

void UmlRelation::setItems(UmlRelationalItem *item1, UmlRelationalItem *item2)
{
    this->_item1 = item1;
    this->_item2 = item2;

    this->_item1->appendRelation(this, this->_item2->positionRelativeTo(this->_item1));
    this->_item2->appendRelation(this, this->_item1->positionRelativeTo(this->_item2));

    QObject::connect(this->_item1, SIGNAL(itemPositionHasChanged()), this, SLOT(itemPositionHasChanged()), Qt::DirectConnection);
    QObject::connect(this->_item2, SIGNAL(itemPositionHasChanged()), this, SLOT(itemPositionHasChanged()), Qt::DirectConnection);

    this->_project = this->_item1->project();
    this->_project->addItem(this);
}

bool UmlRelation::isValid()
{
    return this->_item1 && this->_item2;
}

UmlRelation::RelativeItemPosition UmlRelation::relativeRelationDirection(UmlRelationalItem *baseItem)
{
    UmlRelationalItem *item = baseItem == this->_item1 ? this->_item2 : this->_item1;

    switch(baseItem->positionRelativeTo(item)) {
    case NORTH :
        return SOUTH;
    case SOUTH :
        return NORTH;
    case WEST :
        return EAST;
    default:
        return WEST;
    }
}

QVariant UmlRelation::itemChange(GraphicsItemChange change, const QVariant &value)
{
    if (change == QGraphicsItem::ItemSceneHasChanged) {
        this->_crossLineGraphicsItem.setParentItem(this);
        this->addToGroup(&this->_crossLineGraphicsItem);

        this->_extensionLine1GraphicsItem.setParentItem(this);
        this->addToGroup(&this->_extensionLine1GraphicsItem);

        this->_extensionLine2GraphicsItem.setParentItem(this);
        this->addToGroup(&this->_extensionLine2GraphicsItem);

        this->_nameLabel.setParentItem(this);
        this->addToGroup(&this->_nameLabel);

        this->itemPositionHasChanged();
    }

    return UmlGraphicsItem::itemChange(change, value);
}

void UmlRelation::drawRelationLine()
{
    if (this->isDirected()) {
        this->drawDirectedLine();
    } else {
        this->drawUndirectedLine();
    }
}

QPen UmlRelation::linePenForRelationType(RelationTypes relationType)
{
    QPen pen = this->_pen;

    if ((relationType & UmlRelation::UmlRealizationRelationType) > 0) {
        QVector<qreal> dashPattern;
        dashPattern << 3 << 3;

        pen.setDashPattern(dashPattern);
    }

    return pen;
}

QList<QAction*> UmlRelation::contextActions()
{
    QList<QAction*> actions = UmlGraphicsItem::contextActions();

    if (this->relationIsOfType(UmlAggregationRelationType) ||
            this->relationIsOfType(UmlCompositionRelationType)) {

        QAction *separator1 = new QAction(this);
        separator1->setSeparator(true);
        actions.prepend(separator1);

        QAction *changeDirectionAction = new QAction(tr("change Direction"), this);
        actions.prepend(changeDirectionAction);
        QObject::connect(changeDirectionAction, SIGNAL(triggered()), this, SLOT(changeDirection()));
    }

    if (this->relationIsOfType(UmlAssociationRelationType) ||
            this->relationIsOfType(UmlAggregationRelationType) ||
            this->relationIsOfType(UmlCompositionRelationType)) {

        QAction *isCompositionAction = new QAction(tr("is Composition"), this);
        isCompositionAction->setCheckable(true);
        isCompositionAction->setChecked(this->relationIsOfType(UmlRelation::UmlCompositionRelationType));
        actions.prepend(isCompositionAction);
        QObject::connect(isCompositionAction, SIGNAL(triggered()), this, SLOT(setIsComposition()));

        QAction *isAggregationAction = new QAction(tr("is Aggregation"), this);
        isAggregationAction->setCheckable(true);
        isAggregationAction->setChecked(this->relationIsOfType(UmlRelation::UmlRealizationRelationType));
        actions.prepend(isAggregationAction);
        QObject::connect(isAggregationAction, SIGNAL(triggered()), this, SLOT(setIsAggregation()));

        QAction *isAssociationAction = new QAction(tr("is Association"), this);
        isAssociationAction->setCheckable(true);
        isAssociationAction->setChecked(this->relationIsOfType(UmlRelation::UmlAssociationRelationType));
        actions.prepend(isAssociationAction);
        QObject::connect(isAssociationAction, SIGNAL(triggered()), this, SLOT(setIsAssociation()));
    }

    return actions;
}

QString UmlRelation::directionToString(RelativeItemPosition direction)
{
    switch (direction) {
    case NORTH :
        return "north";
    case SOUTH :
        return "south";
    case WEST :
        return "west";
    case EAST :
        return "east";
    default :
        return "undefined";
    }
}

void UmlRelation::showName(bool visible)
{
    this->_nameLabel.setText(this->_name);
    this->_nameLabel.setVisible(visible);
}

void UmlRelation::drawDirectedLine()
{
    QPoint joinPoint1 = this->_item1->portForRelation(this, this->_item2Direction)->joinPoint();
    QPoint joinPoint2 = this->_item2->portForRelation(this, this->_item1Direction)->joinPoint();

    QLine extensionLine;
    QLine crossLine;

    QPoint p1 = joinPoint1;
    QPoint p2 = joinPoint2;

    UmlRelation::RelativeItemPosition direction = this->_item1->positionRelativeTo(this->_item2);

    extensionLine.setP1(p1);
    extensionLine.setP2(p2);

    crossLine.setP1(p1);
    crossLine.setP2(p2);

    if (direction == NORTH && joinPoint2.y() - joinPoint1.y()  >= 0) {
        extensionLine.setP2(QPoint(p1.x(), p2.y()));
        crossLine.setP1(extensionLine.p2());
    }
    else if (direction == SOUTH && joinPoint1.y() - joinPoint2.y()  >= 0) {
        extensionLine.setP2(QPoint(p1.x(), p2.y()));
        crossLine.setP1(extensionLine.p2());
    }
    else if (direction == WEST && joinPoint2.x() - joinPoint1.x()  >= 0) {
        extensionLine.setP2(QPoint(p2.x(), p1.y()));
        crossLine.setP1(extensionLine.p2());
    }
    else if (direction == EAST && joinPoint1.x() - joinPoint2.x()  >= 0) {
        extensionLine.setP2(QPoint(p2.x(), p1.y()));
        crossLine.setP1(extensionLine.p2());
    }

    this->_extensionLine1GraphicsItem.setLine(extensionLine);
    this->_crossLineGraphicsItem.setLine(crossLine);
}

void UmlRelation::drawUndirectedLine()
{
    RelationPort *port1 = this->_item1->portForRelation(this, this->_item2Direction);
    RelationPort *port2 = this->_item2->portForRelation(this, this->_item1Direction);

    if (!port1 || !port2) {
        return;
    }

    port1->setCardinality(this->_cardinalityItem1);
    port2->setCardinality(this->_cardinalityItem2);
    port1->setRole(this->_roleItem1);
    port2->setRole(this->_roleItem2);

    QPoint joinPoint1 = port1->joinPoint();
    QPoint joinPoint2 = port2->joinPoint();

    QPoint p1 = joinPoint1;
    QPoint p2 = joinPoint2;

    UmlRelation::RelativeItemPosition direction = this->_item1->positionRelativeTo(this->_item2);

    if (direction == UmlRelation::NORTH && joinPoint2.y() - joinPoint1.y()  >= 0) {
        qreal y = abs(p1.y() - p2.y()) / 2;
        p1 += QPoint(0, y);
        p2.setY(p1.y());
    }
    else if (direction == UmlRelation::SOUTH && joinPoint1.y() - joinPoint2.y()  >= 0) {
        qreal y = abs(p1.y() - p2.y()) / 2;
        p1 += QPoint(0, -y);
        p2.setY(p1.y());
    }
    else if (direction == UmlRelation::WEST && joinPoint2.x() - joinPoint1.x()  >= 0) {
        qreal x = abs(p1.x() - p2.x()) / 2;
        p1 += QPoint(x, 0);
        p2.setX(p1.x());
    }
    else if (direction == UmlRelation::EAST && joinPoint1.x() - joinPoint2.x()  >= 0) {
        qreal x = abs(p1.x() - p2.x()) / 2;
        p1 += QPoint(-x, 0);
        p2.setX(p1.x());
    }

    QLine extensionLine1 = QLine(joinPoint1, p1);
    QLine extensionLine2 = QLine(joinPoint2, p2);
    QLine crossLine = QLine(p1, p2);

    this->_extensionLine1GraphicsItem.setLine(extensionLine1);
    this->_extensionLine2GraphicsItem.setLine(extensionLine2);
    this->_crossLineGraphicsItem.setLine(crossLine);

    this->drawName(crossLine.p1(), crossLine.p2(), direction);
}

void UmlRelation::drawName(QPoint p1, QPoint p2, UmlRelation::RelativeItemPosition &direction)
{
    QPointF pos;
    qreal crossLength = this->_crossLineGraphicsItem.line().length();

    if (direction == UmlRelation::NORTH || direction == UmlRelation::SOUTH) {
        qreal textWidth = this->_nameLabel.boundingRect().width();
        pos = p1.x() > p2.x() ? p2 : p1;
        pos += QPointF((crossLength - textWidth) / 2, 0);
        pos.setY(pos.y() - this->_nameLabel.boundingRect().height() / 2);
    } else {
        qreal textHeight = this->_nameLabel.boundingRect().height();
        pos = p1.y() > p2.y() ? p2 : p1;
        pos += QPointF(0, (crossLength - textHeight) / 2);
        pos.setX(pos.x() - this->_nameLabel.boundingRect().width() / 2);
    }

    this->_nameLabel.setPos(pos);
}

void UmlRelation::itemPositionHasChanged()
{
    if (this->_item1->positionRelativeTo(this->_item2) == this->_item2->positionRelativeTo(this->_item1)) {
        return;
    }

    this->_item1Direction = this->_item1->positionRelativeTo(this->_item2);
    this->_item2Direction = this->_item2->positionRelativeTo(this->_item1);

    UmlRelation::RelativeItemPosition storedItem1Dir = this->_item1->storedRelationDirection(this);
    UmlRelation::RelativeItemPosition storedItem2Dir = this->_item2->storedRelationDirection(this);

    if (this->_item2Direction != storedItem1Dir) {
        this->_item1->moveRelation(this, storedItem1Dir, this->_item2Direction);
    }

    if (this->_item1Direction != storedItem2Dir) {
        this->_item2->moveRelation(this, storedItem2Dir, this->_item1Direction);
    }

    this->drawRelationLine();

    this->_port1 = this->_item1->portForRelation(this, this->_item2Direction);
    this->_port2 = this->_item2->portForRelation(this, this->_item1Direction);

    this->_port1->setRelation(this);
    this->_port2->setRelation(this);
}

void UmlRelation::relationHoverEnter(QGraphicsSceneHoverEvent *event)
{
    qDebug() << "relation hover enter";
}

void UmlRelation::relationHoverLeave(QGraphicsSceneHoverEvent *event)
{
    qDebug() << "relation hover leave";
}

void UmlRelation::relationHoverMove(QGraphicsSceneHoverEvent *event)
{
    qDebug() << "relation hover move";
}

void UmlRelation::relationMouseDoubleClick(QGraphicsSceneMouseEvent *event)
{
    if (event->button() != Qt::LeftButton) {
        return;
    }

    if (this->relationIsOfType(UmlRelation::UmlGeneralizationRelationType) ||
        this->relationIsOfType(UmlRelation::UmlRealizationRelationType)) {
        return;
    }

    RelationEditor *editor = new RelationEditor();
    editor->setRelation(this);

    if (editor->exec() == QDialog::Accepted) {
        this->itemPositionHasChanged();
    }
}

void UmlRelation::remove()
{
    this->_item1->removeRelation(this, this->_item2Direction);
    this->_item2->removeRelation(this, this->_item1Direction);

    delete this;
}

void UmlRelation::setIsGeneralization()
{
    this->_relationType = 0;
    this->_relationType = UmlRelation::UmlDirectedRelationType;
    this->_relationType |= UmlRelation::UmlGeneralizationRelationType;

    this->_item1->removeRelation(this, this->_item1Direction);
    this->_item2->removeRelation(this, this->_item2Direction);

    this->setItems(this->_item1, this->_item2);
}

void UmlRelation::setIsRealization()
{
    this->_relationType = 0;
    this->_relationType = UmlRelation::UmlDirectedRelationType;
    this->_relationType |= UmlRelation::UmlRealizationRelationType;

    this->_item1->removeRelation(this, this->_item1Direction);
    this->_item2->removeRelation(this, this->_item2Direction);

    this->setItems(this->_item1, this->_item2);
}

void UmlRelation::setIsAssociation()
{
    this->setRelationType(UmlRelation::UmlAssociationRelationType);
}

void UmlRelation::setIsAggregation()
{
    this->setRelationType(UmlRelation::UmlAggregationRelationType);
}

void UmlRelation::setIsComposition()
{
    this->setRelationType(UmlRelation::UmlCompositionRelationType);
}

void UmlRelation::changeDirection()
{
    this->_item1->removeRelation(this, this->_item1Direction);
    this->_item2->removeRelation(this, this->_item2Direction);

    UmlRelationalItem *item1 = this->_item1;
    UmlRelationalItem *item2 = this->_item2;

    this->setItems(item2, item1);
}

void UmlRelation::changeRelationType(UmlRelation::RelationTypes relationType)
{
    QPen pen = this->linePenForRelationType(relationType);

    this->_crossLineGraphicsItem.setPen(pen);
    this->_extensionLine1GraphicsItem.setPen(pen);
    this->_extensionLine2GraphicsItem.setPen(pen);
}
